/**
 * 
 */
package name.yzhu.httpserver.handle;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import name.yzhu.common.CommonConstant;
import name.yzhu.common.lang.LangUtil;
import name.yzhu.httpserver.HttpRequestType;
import name.yzhu.httpserver.HttpReturnData;
import name.yzhu.httpserver.HttpServerResponseException;
import name.yzhu.httpserver.log.LogEntity;
import name.yzhu.httpserver.log.LogHandler;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.jetty.server.Request;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.net.HttpHeaders;

/**
 * @author yhzhu
 *
 */
public class HttpserverHandle extends AbstractHttpserverHandle{
	private static final Log LOG = LogFactory.getLog(HttpserverHandle.class);
	private List<HttpserverHandleProcess> handles = Lists.newArrayList();
	
	@Override
	public void handle(String target, Request baseRequest,
			HttpServletRequest req, HttpServletResponse res)
			throws IOException, ServletException {

		LOG.debug("target = "+target);
		HttpRequestType type = HttpRequestType.byName(req.getMethod());
		HttpReturnData retData = new HttpReturnData();
		if (rootPermission != null && !rootPermission.check(req, type, retData )){
			String encode = retData.getHead(HttpHeaders.CONTENT_ENCODING);
			if (encode == null)
				encode = CommonConstant.UTF_8_STRING;
			res.getWriter().printf(new String(retData.getData(), encode));
			res.flushBuffer();
			return;
		}
		
		for (HttpserverHandleProcess process : handles){
			String pattern = process.getPattern();
			List<String> extParam = Lists.newArrayList();
			if (processPattern(target, pattern, extParam )){
				RequestParameter param = process.getRequstParameterFactory(type);
				
				param.setReq(req);
				param.setRes(res);
				request(process, param, extParam, type, req, res);
				
				return ;
			}
		}
		
		
	}

	private void request(HttpserverHandleProcess handle, RequestParameter param, List<String> extParam, 
			HttpRequestType type, HttpServletRequest req, HttpServletResponse res) {
		LogHandler log = handle.getLogHandler(type);
		log.begin();
		log.setRequest(req);
		log.setRequestType(type);
		try{
			HttpReturnData retData = new HttpReturnData();
			try{
				if (handle.getHttpPermissionFactory().check(req, type, retData)){
					parseExtParam(handle, param, extParam, req);
					setParamFields(handle, param, req);
					switch (type) {
						case Get:
							retData = handle.get(param);
							break;
						case Post:
							retData = handle.post(param);
							break;
						case Put:
							retData = handle.put(param);
							break;
						case Delete:
							retData = handle.delete(param);
							break;
						default:
							retData.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
							break;
					}
					
				}
			}
			catch(HttpServerResponseException e){
				retData = handle.getExceptionData(e.getCode(), e.getMessage());
			}
			log.setRetuData(retData);
			res.setStatus(retData.getStatus());
			res.setContentLength(retData.getData().length);
			res.setContentType("text/html; charset=UTF-8");
			res.setCharacterEncoding(CommonConstant.UTF_8_STRING);
			for (Entry<String, String> head  : retData.heads()){
				res.addHeader(head.getKey(), head.getValue());
			}
			String encode = retData.getHead(HttpHeaders.CONTENT_ENCODING);
			if (encode == null)
				encode = CommonConstant.UTF_8_STRING;
			res.getWriter().printf(new String(retData.getData(), encode));
			res.flushBuffer();
			LogEntity entity = log.getLogEntity();
			handle.paraseRetData(retData, entity);
		} catch (Exception e) {
			LOG.error(e.getMessage(), e);
			try {
				res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
			} catch (IOException e1) {
				LOG.error(e.getMessage(), e1);
			}
		}
		finally{
			log.end();
			
			log.persist(log.getLogEntity());
		}
	}

	private void parseExtParam(HttpserverHandleProcess process,
			RequestParameter param, List<String> extParam,
			HttpServletRequest req) throws HttpServerResponseException {
		
		List<String> names = param.getPathParamNames();
		for(int i=0; i<names.size(); i++){
			Field field = getField(param.getClass(), names.get(i));
			field.setAccessible(true);
			if (i < extParam.size()){
				Object obj = LangUtil.converte2PrimaryType(field.getType(), extParam.get(i));
				try {
					field.set(param, obj );
				} catch (IllegalArgumentException e) {
					throw new HttpServerResponseException(HttpserverHandleProcess.INVALID_DATA_TYPE, "invalid data type: "+extParam.get(i));
				} catch (IllegalAccessException e) {
					throw new HttpServerResponseException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
				}
			}
		}
		
	}

	@SuppressWarnings("unchecked")
	private void setParamFields(HttpserverHandleProcess process, RequestParameter param, 
			HttpServletRequest req) throws HttpServerResponseException {
		Class<? extends RequestParameter> cls = param.getClass();
		do {
			for (Field field : cls.getDeclaredFields()){
				if (field.isAnnotationPresent(RequestParamAnno.class)){
					RequestParamAnno anno = field.getAnnotation(RequestParamAnno.class);
					String name = anno.name();
					if (Strings.isNullOrEmpty(name))
						name = field.getName();
					String value = req.getParameter(name);
					if ((Strings.isNullOrEmpty(value) || value.equals("\"\"") ||value.equals("''"))){
						Set<String>required = param.getRequiredParams();
						if (required != null && required.size() > 0 && required.contains(name)){
							throw new HttpServerResponseException(HandleURLProcess.NOT_ALLOW_NULL, name+" filed can't be empty");
						}
					}
					else{
						try{
							Object obj = LangUtil.converte2PrimaryType(field.getType(), value);
							field.setAccessible(true);
							field.set(param, obj);
						}
						catch(IllegalArgumentException e){
							throw new HttpServerResponseException(HttpserverHandleProcess.INVALID_DATA_TYPE, "invalid data type: "+name);
						} catch (IllegalAccessException e) {
							throw new HttpServerResponseException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
						}
					}
				}
			}
		}while((cls = (Class<? extends RequestParameter>) cls.getSuperclass()) != null);
	}

	@SuppressWarnings("unchecked")
	private Field getField(Class<? extends RequestParameter> cls,
			String name) {
		try {
			return cls.getDeclaredField(name);
		} catch (NoSuchFieldException e) {
			return getField((Class<? extends RequestParameter>) cls.getSuperclass(), name);
		}
	}

	public List<HttpserverHandleProcess> getHandles() {
		return handles;
	}

	public void addHandles(HttpserverHandleProcess handle) {
		this.handles.add(handle);
	}

}
